package com.bytedance.common.utility.reflect;

import android.util.Log;

import com.bytedance.common.utility.Logger;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

public final class Reflect {
    public static class NULL {
    }

    public final Object target;
    private final boolean isStatic;

    private Reflect(Class clazz) {
        this.target = clazz;
        this.isStatic = true;
    }

    private Reflect(Object target) {
        this.target = target;
        this.isStatic = false;
    }

    public static Reflect on(Object object) {
        return new Reflect(object);
    }

    public final Reflect call(String name, Class[] parameterTypes, Object[] args) throws ReflectException {
        try {
            Method method = this.exactMethod(name, parameterTypes);
            return on(method, this.target, args);
        } catch (NoSuchMethodException e0) {
            try {
                Method method = similarMethod(name, parameterTypes);
                return on(method, target, args);
            } catch (NoSuchMethodException e) {
                throw new ReflectException(e);
            }
        }
    }

    public final Object getFieldValue(String fieldName, Class[] arg3) throws ReflectException {
        return this.get(fieldName, arg3).target;
    }

    public final Reflect set(String fieldName, Object arg5) throws ReflectException {
        try {
            Field v0_1 = this.field(fieldName, new Class[0]);
            Object v1 = this.target;
            if ((arg5 instanceof Reflect)) {
                arg5 = ((Reflect) arg5).target;
            }
            v0_1.set(v1, arg5);
            return this;
        } catch (Exception v0) {
            throw new ReflectException(v0);
        }
    }

    public final Reflect call(String method) throws ReflectException {
        return this.call(method, null, new Object[0]);
    }

    public static Reflect onClass(Class<?> clazz) {
        return new Reflect(clazz);
    }

    public final Reflect create() throws ReflectException {
        return this.create(new Object[0]);
    }

    private static Reflect on(Constructor constructor, Object... args) throws ReflectException {
        try {
            return on(accessible(constructor).newInstance(args));
        } catch (Exception e) {
            throw new ReflectException(e);
        }
    }

    /**
     * Conveniently render an {@link AccessibleObject} accessible.
     * <p>
     * To prevent {@link SecurityException}, this is only done if the argument
     * object and its declaring class are non-public.
     *
     * @param accessible The object to render accessible
     * @return The argument object rendered accessible
     */
    public static <T extends AccessibleObject> T accessible(T accessible) {
        if (accessible == null) {
            return null;
        }

        if (accessible instanceof Member) {
            Member member = (Member) accessible;

            if (Modifier.isPublic(member.getModifiers()) &&
                    Modifier.isPublic(member.getDeclaringClass().getModifiers())) {

                return accessible;
            }
        }

        // [jOOQ #3392] The accessible flag is set to false by default, also for public members.
        if (!accessible.isAccessible())
            accessible.setAccessible(true);

        return accessible;
    }

    private static Reflect on(Method method, Object obj, Object[] args) throws ReflectException {
        Reflect v0_1;
        try {
            accessible(method);
            if (method.getReturnType() == Void.TYPE) {
                method.invoke(obj, args);
                v0_1 = on(obj);
            } else {
                v0_1 = on(method.invoke(obj, args));//method: currentActivityThread  arg3:ActivityThread   empty
                // method: private static [Ldalvik.system.DexPathList$Element; dalvik.system.DexPathList.makePathElements(java.util.List,java.io.File,java.util.List)
                // arg3: DexPathList[[zip file "/data/app/com.ss.android.article.browser-1/base.apk", zip file "/data/user/0/com.ss.android.article.browser/files/ss_plugins/com.bytedance.common.plugin.cronet/dex/com.bytedance.common.plugin.cronet.apk.classes1.zip"],nativeLibraryDirectories=[/data/app/com.ss.android.article.browser-1/lib/arm, /data/app/com.ss.android.article.browser-1/base.apk!/lib/armeabi, /data/user/0/com.ss.android.article.browser/files/ss_plugins/com.bytedance.common.plugin.cronet/lib, /vendor/lib, /system/lib]]

                // public static com.bytedance.common.plugin.faces.CronetProxy com.bytedance.common.plugin.faces.CronetProxy.inst()
                // class com.bytedance.common.plugin.faces.CronetProxy
            }
            return v0_1;
        } catch (Exception v0) {
            throw new ReflectException(((Throwable) v0));
        }
    }

    private Reflect create(Object... arg8) throws ReflectException {
        Reflect reflect;
        int index = 0;
        Class[] classes = new Class[0];
        try {
            reflect = on(this.type().getDeclaredConstructor(classes), arg8);
        } catch (NoSuchMethodException e) {
            Constructor[] constructors = this.type().getDeclaredConstructors();
            int length = constructors.length;
            while (index < length) {
                Constructor constructor = constructors[index];
                if (match(constructor.getParameterTypes(), classes)) {
                    return on(constructor, arg8);
                }
                ++index;
            }
            throw new ReflectException(e);
        }

        return reflect;
    }

    private static boolean match(Class<?>[] declaredTypes, Class<?>[] actualTypes) {
        if (declaredTypes.length == actualTypes.length) {
            for (int i = 0; i < actualTypes.length; ++i) {
                if (actualTypes[i] == NULL.class) {
                    continue;
                }
                if (!wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Searches a method with a similar signature as desired using
     * {@link #isSimilarSignature(java.lang.reflect.Method, String, Class[])}.
     * <p>
     * First public methods are searched in the class hierarchy, then private
     * methods on the declaring class. If a method could be found, it is
     * returned, otherwise a {@code NoSuchMethodException} is thrown.
     */
    private Method similarMethod(String name, Class<?>[] types) throws NoSuchMethodException {
        Class<?> t = type();

        // first priority: find a public method with a "similar" signature in class hierarchy
        // similar interpreted in when primitive argument types are converted to their wrappers
        for (Method method : t.getMethods()) {
            if (isSimilarSignature(method, name, types)) {
                return method;
            }
        }

        // second priority: find a non-public method with a "similar" signature on declaring class
        do {
            for (Method method : t.getDeclaredMethods()) {
                if (isSimilarSignature(method, name, types)) {
                    return method;
                }
            }

            t = t.getSuperclass();
        }
        while (t != null);

        throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + ".");
    }

    private static boolean isSimilarSignature(Method possiblyMatchingMethod, String desiredMethodName, Class<?>[] desiredParamTypes) {
        return possiblyMatchingMethod.getName().equals(desiredMethodName) && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);
    }

    public static Class<?> forName(String name) throws ReflectException {
        try {
            return Class.forName(name);
        } catch (Exception e) {
            throw new ReflectException(e);
        }
    }

    public final Reflect get(String fieldName, Class[] classes) throws ReflectException {
        try {
            Object obj = this.field(fieldName, classes).get(this.target);
            return on(obj);
        } catch (Exception v0) {
            v0.printStackTrace();
            throw new ReflectException(v0);
        }
    }

    private Class type() {
        Class v0_1;
        if (this.isStatic) {
            v0_1 = (Class) this.target;
        } else {
            v0_1 = this.target.getClass();
        }

        return v0_1;
    }

    /**
     * Get a wrapper type for a primitive type, or the argument type itself, if
     * it is not a primitive type.
     */
    public static Class<?> wrapper(Class<?> type) {
        if (type == null) {
            return null;
        } else if (type.isPrimitive()) {
            if (boolean.class == type) {
                return Boolean.class;
            } else if (int.class == type) {
                return Integer.class;
            } else if (long.class == type) {
                return Long.class;
            } else if (short.class == type) {
                return Short.class;
            } else if (byte.class == type) {
                return Byte.class;
            } else if (double.class == type) {
                return Double.class;
            } else if (float.class == type) {
                return Float.class;
            } else if (char.class == type) {
                return Character.class;
            } else if (void.class == type) {
                return Void.class;
            }
        }

        return type;
    }

    public final Method exactMethod(String methodName, Class[] parameterTypes) throws NoSuchMethodException {
        Method method;
        Class clazz = this.type();
        try {
            method = clazz.getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException methodException) {
            do {
                try {
                    return clazz.getDeclaredMethod(methodName, parameterTypes);
                } catch (NoSuchMethodException noSuchMethodException) {
                    clazz = clazz.getSuperclass();
                }
            } while (clazz != null);
            clazz = this.type();
            Method[] methods = clazz.getMethods();
            int methodLength = methods.length;
            for (int j = 0; j < methodLength; ++j) {
                method = methods[j];
                if (methodName.equals(method.getName())) {
                    Class[] parameterTypes1 = method.getParameterTypes();
                    if (Logger.debug()) {
                        int index = 1;
                        int length = parameterTypes1.length;
                        for (int i = 0; i < length; ++i) {
                            Logger.d("Plugin", "Reflect type = " + clazz + " method = " + methodName + " paramTypes " + index + " = " + parameterTypes1[i].getName());
                            ++index;
                        }
                    }
                    if (parameterTypes1 == null) {
                        continue;
                    }
                    if (parameterTypes1.length == parameterTypes.length) {
                        return method;
                    }
                }
            }
            throw new NoSuchMethodException();
        }
        return method;
    }

    private Field field(String fieldName, Class[] classes) throws ReflectException {
        Class clazz = this.type();
        if (classes == null || classes.length <= 0) {
            try {
                return clazz.getField(fieldName);
            } catch (NoSuchFieldException noSuchFieldException) {
                while (true) {
                    try {
                        return accessible(clazz.getDeclaredField(fieldName));
                    } catch (NoSuchFieldException e) {
                        clazz = clazz.getSuperclass();
                        if (clazz == null) {
                            throw new ReflectException(noSuchFieldException);
                        }
                    }
                }
            }
        }
        clazz = this.type();
        do {
            try {
                Field[] fields = clazz.getDeclaredFields();
                int length = fields.length;
                int index = 0;
                while (index < length) {
                    Field field = fields[index];
                    Class fieldType = field.getType();
                    if (fieldType != null && fieldType.getName().equals(classes[0].getName()) && field.getName().equals(fieldName)) {
                        return accessible(field);
                    }
                    index++;
                    continue;
                }
                clazz = clazz.getSuperclass();
                if (clazz == null) {
                    throw new ReflectException("NoSuchFieldException");
                } else {
                    continue;
                }
            } catch (Exception exception) {
                clazz = clazz.getSuperclass();
                if (clazz == null) {
                    throw new ReflectException("NoSuchFieldException");
                } else {
                    continue;
                }
            }
        } while (true);
    }

    public final boolean equals(Object obj) {
        return obj instanceof Reflect && this.target.equals(((Reflect) obj).target);
    }

    public final int hashCode() {
        return this.target.hashCode();
    }

    public final String toString() {
        return this.target.toString();
    }
}

